#include "warray/warray_ops.h"
#include "test.h"

#include <iostream>

#include "custom_type.h"
#include "helper.h"

template<typename T, size_t N, typename ARRAY_OP, typename EL_OP>
bool generic_operator_test(ARRAY_OP aop, EL_OP eop)
{
	warp::arraynd<T,N> x(extents_gen<N>(3));
	warp::arraynd<T,N> y(extents_gen<N>(3));
	warp::arraynd<T,N> z;
	
	fill_nonzero(x);
	fill_nonzero(y);
	z = aop(x,y);
		
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	return array_equal(x,y,z,eop);
}

template<typename T, size_t N>
void test_inplace_operations()
{
	warp::arraynd<T,N> x(extents_gen<N>(3));
	warp::arraynd<T,N> y(extents_gen<N>(3));
	warp::arraynd<T,N> z;
	
	fill_nonzero(x);
	fill_nonzero(y);
	
	z = x.copy();
	z += y;
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	BOOST_CHECK( (array_equal(x,y,z,std::plus<T>())) );
	
	z = x.copy();
	z -= y;
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	BOOST_CHECK( (array_equal(x,y,z,std::minus<T>())) );
	
	z = x.copy();
	z *= y;
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	BOOST_CHECK( (array_equal(x,y,z,std::multiplies<T>())) );
	
	z = x.copy();
	z /= y;
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	BOOST_CHECK( (array_equal(x,y,z,std::divides<T>())) );
}

template<typename T, size_t N>
void test_complex_operations()
{
	warp::arraynd<std::complex<T>,N> x(extents_gen<N>(3));
	warp::arraynd<std::complex<T>,N> y(extents_gen<N>(3));
	warp::arraynd<std::complex<T>,N> z(extents_gen<N>(3));
	
	fill_nonzero(x);
	fill_nonzero(y);
	
	warp::mulconj(x,y,z);
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	BOOST_CHECK( array_equal(x,y,z,warp::detail::multiplies_conj<T>()) );
	
	z = x.copy();
	warp::mulconj(z,y,z);
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	BOOST_CHECK( array_equal(x,y,z,warp::detail::multiplies_conj<T>()) );
}

typedef boost::mpl::list< float,double,custom_type,std::complex<float>,std::complex<double> > test_types;
typedef boost::mpl::list< float,double > test_complex_types;

BOOST_AUTO_TEST_CASE_TEMPLATE( operator_plus, T, test_types )
{
	BOOST_CHECK( (generic_operator_test<T,1>( std::plus< warp::arraynd<T,1> >(), std::plus<T>() )) );
	BOOST_CHECK( (generic_operator_test<T,2>( std::plus< warp::arraynd<T,2> >(), std::plus<T>() )) );
	BOOST_CHECK( (generic_operator_test<T,3>( std::plus< warp::arraynd<T,3> >(), std::plus<T>() )) );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( operator_minus, T, test_types )
{
	BOOST_CHECK( (generic_operator_test<T,1>( std::minus< warp::arraynd<T,1> >(), std::minus<T>() )) );
	BOOST_CHECK( (generic_operator_test<T,2>( std::minus< warp::arraynd<T,2> >(), std::minus<T>() )) );
	BOOST_CHECK( (generic_operator_test<T,3>( std::minus< warp::arraynd<T,3> >(), std::minus<T>() )) );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( operator_multiplies, T, test_types )
{
	BOOST_CHECK( (generic_operator_test<T,1>( std::multiplies< warp::arraynd<T,1> >(), std::multiplies<T>() )) );
	BOOST_CHECK( (generic_operator_test<T,2>( std::multiplies< warp::arraynd<T,2> >(), std::multiplies<T>() )) );
	BOOST_CHECK( (generic_operator_test<T,3>( std::multiplies< warp::arraynd<T,3> >(), std::multiplies<T>() )) );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( operator_divides, T, test_types )
{
	BOOST_CHECK( (generic_operator_test<T,1>( std::divides< warp::arraynd<T,1> >(), std::divides<T>() )) );
	BOOST_CHECK( (generic_operator_test<T,2>( std::divides< warp::arraynd<T,2> >(), std::divides<T>() )) );
	BOOST_CHECK( (generic_operator_test<T,3>( std::divides< warp::arraynd<T,3> >(), std::divides<T>() )) );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( inplace_operations, T, test_types )
{
	test_inplace_operations<T,1>();
	test_inplace_operations<T,2>();
	test_inplace_operations<T,3>();
}

BOOST_AUTO_TEST_CASE_TEMPLATE( complex_operations, T, test_complex_types )
{
	test_complex_operations<T,1>();
	test_complex_operations<T,2>();
	test_complex_operations<T,3>();
}

BOOST_AUTO_TEST_CASE_TEMPLATE( array_conj, T, test_complex_types )
{
	typedef std::complex<T> C;
	warp::arraynd<C,1> x(warp::extents[16]);
	fill_nonzero(x);
	
	warp::arraynd<C,1> z(x.dims()); 
	warp::conj(x,z);
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	BOOST_CHECK( (array_equal(x,z,warp::detail::choose_conj<T>())) );
	
	z = warp::conj(x);
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	BOOST_CHECK( (array_equal(x,z,warp::detail::choose_conj<T>())) );
	
	z = x.copy();
	warp::conj(z,z);
	BOOST_CHECK_EQUAL(x.dims(),z.dims());
	BOOST_CHECK( (array_equal(x,z,warp::detail::choose_conj<T>())) );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( add_const, T, test_types )
{
	warp::arraynd<T,1> x(warp::extents[16]);
	T y = T(2);
	fill_nonzero(x);
	
	warp::arraynd<T,1> z(x.dims()); 
	warp::add_const(x,y,z);
	BOOST_CHECK( array_equal(x,z,std::bind2nd(std::plus<T>(),y)) );
	
	z = x.copy();
	warp::add_const(z,y,z);
	BOOST_CHECK( array_equal(x,z,std::bind2nd(std::plus<T>(),y)) );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( sub_const, T, test_types )
{
	warp::arraynd<T,1> x(warp::extents[16]);
	T y = T(2);
	fill_nonzero(x);
	
	warp::arraynd<T,1> z(x.dims()); 
	warp::sub_const(x,y,z);
	BOOST_CHECK( array_equal(x,z,std::bind2nd(std::minus<T>(),y)) );
	
	z = x.copy();
	warp::sub_const(z,y,z);
	BOOST_CHECK( array_equal(x,z,std::bind2nd(std::minus<T>(),y)) );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( neg, T, test_types )
{
	warp::arraynd<T,1> x(warp::extents[16]);
	fill_nonzero(x);
	
	warp::arraynd<T,1> y(x.dims()); 
	warp::neg(x,y);
	BOOST_CHECK( array_equal(x,y,std::negate<T>()) );
	
	y = x.copy();
	warp::neg(y,y);
	BOOST_CHECK( array_equal(x,y,std::negate<T>()) );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( mul_const, T, test_types )
{
	warp::arraynd<T,1> x(warp::extents[16]);
	T y = T(2);
	fill_nonzero(x);
	
	warp::arraynd<T,1> z(x.dims()); 
	warp::mul_const(x,y,z);
	BOOST_CHECK( array_equal(x,z,std::bind2nd(std::multiplies<T>(),y)) );
	
	z = x.copy();
	warp::mul_const(z,y,z);
	BOOST_CHECK( array_equal(x,z,std::bind2nd(std::multiplies<T>(),y)) );
}

BOOST_AUTO_TEST_CASE_TEMPLATE( div_const, T, test_types )
{
	warp::arraynd<T,1> x(warp::extents[16]);
	T y = T(2);
	fill_nonzero(x);
	
	warp::arraynd<T,1> z(x.dims()); 
	warp::div_const(x,y,z);
	BOOST_CHECK( array_equal(x,z,std::bind2nd(std::divides<T>(),y)) );
	
	z = x.copy();
	warp::div_const(z,y,z);
	BOOST_CHECK( array_equal(x,z,std::bind2nd(std::divides<T>(),y)) );
}


